const { Jimp } = require('jimp');
const { intToRGBA } = require('@jimp/utils');
const fs = require('fs').promises;
const path = require('path');

class MapTileManager {
    constructor(app, tileSystem) {
        if (!tileSystem) {
            throw new Error('tileSystem 必须被实例化');
        }
        this.app = app;
        this.tileSystem = tileSystem;
        this.processedTiles = new Set();
        this.missingTiles = [];
    }

    async loadScreenshots(dir) {
        try {
            const files = await fs.readdir(dir);
            const imageFiles = files.filter((file) => file.endsWith('.png'));
            const screenshots = [];

            for (const imageFile of imageFiles) {
                const imagePath = path.join(dir, imageFile);
                const metadataFile = imageFile.replace('.png', '.json');
                const metadataPath = path.join(dir, metadataFile);

                try {
                    const metadataRaw = await fs.readFile(metadataPath, 'utf-8');
                    const metadata = JSON.parse(metadataRaw);
                    screenshots.push({ imagePath, metadata });
                } catch (err) {
                    console.warn(`无法读取元数据文件: ${metadataPath}, 跳过 ${imageFile}`);
                }
            }

            return screenshots;
        } catch (error) {
            console.error('读取输入目录时出错:', error);
            return [];
        }
    }

    getTileCenter(centerLat, centerLng, zoom, imageWidth, imageHeight, x, y, scale) {
        const tileKey = `${zoom}/${x}/${y}`;
        const tileBounds = this.tileSystem.getTileBounds(x, y, zoom);
        const { nw, se } = tileBounds;

        const tileCenter = {
            lat: (nw.lat + se.lat) / 2,
            lng: (nw.lng + se.lng) / 2
        };

        const deltaLat = tileCenter.lat - centerLat;
        const deltaLng = tileCenter.lng - centerLng;

        const deltaY = deltaLat * 111320;
        const deltaX = deltaLng * ((40075016.686 * Math.cos(this.tileSystem.toRadians(centerLat))) / 360);

        const pixelX = deltaX / scale + imageWidth / 2;
        const pixelY = -deltaY / scale + imageHeight / 2;

        return {
            pixelX,
            pixelY
        };
    }

    async splitImage(image, rects, zoom, outputDir) {
        try {
            const imgWidth = image.bitmap.width;
            const imgHeight = image.bitmap.height;

            for (let i = 0; i < rects.length; i++) {
                const { idx, idy, x, y, width, height } = rects[i];
                if (width <= 0 || height <= 0) {
                    console.log(`Rectangle ${i} has non-positive dimensions, skipping...`);
                    continue;
                }

                const left = Math.max(0, x);
                const top = Math.max(0, y);
                const right = Math.min(imgWidth, x + width);
                const bottom = Math.min(imgHeight, y + height);

                if (right <= left || bottom <= top) {
                    console.log(`Rectangle [${left}, ${top}, ${right}, ${bottom}] does not intersect with image, skipping...`);
                    continue;
                }

                const cropWidth = right - left;
                const cropHeight = bottom - top;

                const outputImagePath = path.join(outputDir, `zoom${zoom}`, `${idx}`, `${idy}.png`);
                await fs.mkdir(path.dirname(outputImagePath), { recursive: true });

                // console.log('Cropping with:', left, top, cropWidth, cropHeight);
                const extracted = image.clone().crop({ x: left, y: top, w: cropWidth, h: cropHeight });

                const compositeImage = new Jimp({ width: 256, height: 256, color: 0x00000000 });
                compositeImage.composite(extracted, x < 0 ? -x : 0, y < 0 ? -y : 0);

                const currentImageBuffer = await compositeImage.getBuffer('image/png');

                console.log(`\t处理[${idx}-${idy}]`);
                await this.updateLocalImage(outputImagePath, currentImageBuffer, this.tileSystem.tileSize);
            }
            console.log('All images processed successfully!');
        } catch (error) {
            console.error('Error processing the image:', error);
        }
    }

    async updateLocalImage(outputImagePath, currentImageBuffer, tileSize) {
        try {
            const localImageExists = await fs
                .access(outputImagePath)
                .then(() => true)
                .catch(() => false);

            if (localImageExists) {
                const localImage = await Jimp.read(outputImagePath);
                const isLocalImageComplete = await this.isImageComplete(localImage, tileSize);

                if (isLocalImageComplete) {
                    console.log(`\tTile is complete, skipping...\n`);
                    return;
                }

                const currentImage = await Jimp.read(currentImageBuffer);
                localImage.composite(currentImage, 0, 0);

                await localImage.write(outputImagePath);
                console.log(`\tTile updated successfully!\n`);
            } else {
                const currentImage = await Jimp.read(currentImageBuffer);
                await currentImage.write(outputImagePath);
                console.log(`\tTile created successfully!\n`);
            }
        } catch (error) {
            console.error('Error updating local image:', error);
        }
    }

    async isImageComplete(image, tileSize) {
        try {
            if (image.bitmap.width !== tileSize || image.bitmap.height !== tileSize) {
                return false;
            }

            for (let x = 0; x < image.bitmap.width; x++) {
                for (let y = 0; y < image.bitmap.height; y++) {
                    const { a } = intToRGBA(image.getPixelColor(x, y));
                    if (a === 0) {
                        return false;
                    }
                }
            }

            return true;
        } catch (error) {
            console.error('Error checking image completeness:', error);
            return false;
        }
    }

    async processScreenshot(imagePath, metadata, outputDir) {
        const { centerLat, centerLng, zoom } = metadata;
        try {
            const image = await Jimp.read(imagePath);
            const { width: imageWidth, height: imageHeight } = image.bitmap;

            const scale = this.tileSystem.getMetersPerPixel(centerLat, zoom);
            if (!scale || typeof scale !== 'number') {
                console.error(`无法计算每像素的地面距离（scale） for zoom level ${zoom} and latitude ${centerLat}`);
                this.missingTiles.push({ zoom, x: null, y: null, reason: '无法计算 scale' });
                return;
            }

            const tileRange = this.tileSystem.calculateTileCoverage(centerLat, centerLng, zoom, imageWidth, imageHeight, scale);
            const { minX, maxX, minY, maxY } = tileRange;

            console.log('\n=========================================');
            console.log(`处理截图: ${imagePath}`);
            console.log(`切片范围 X: ${minX} - ${maxX}, Y: ${minY} - ${maxY}`);
            console.log(`切片覆盖 X: ${maxX - minX}, Y: ${maxY - minY}`);

            let coords_x = [];
            let coords_y = [];

            for (let x = minX; x <= maxX; x++) {
                let center = this.getTileCenter(centerLat, centerLng, zoom, imageWidth, imageHeight, x, minY, scale);
                coords_x.push({
                    idx: x,
                    px: center.pixelX
                });
            }

            for (let y = minY; y <= maxY; y++) {
                let center = this.getTileCenter(centerLat, centerLng, zoom, imageWidth, imageHeight, minX, y, scale);
                coords_y.push({
                    idy: y,
                    py: center.pixelY
                });
            }

            let rect_size = this.tileSystem.tileSize;
            let tiles = [];
            for (let i = 0; i < coords_x.length; i++) {
                for (let j = 0; j < coords_y.length; j++) {
                    tiles.push({
                        idx: coords_x[i].idx,
                        idy: coords_y[j].idy,
                        x: Math.floor(coords_x[i].px - rect_size / 2),
                        y: Math.floor(coords_y[j].py - rect_size / 2),
                        width: rect_size,
                        height: rect_size
                    });
                }
            }

            await this.splitImage(image, tiles, zoom, outputDir);
        } catch (err) {
            console.error('处理单个截图失败:', err);
            this.missingTiles.push({ zoom, x: null, y: null, reason: '处理失败', error: err.message });
        }
    }

    async batchProcess(screenshots, outputDir) {
        for (const screenshot of screenshots) {
            const { imagePath, metadata } = screenshot;

            this.app.server.broadcast(
                JSON.stringify({
                    event: 'import_status',
                    data: `开始处理截图：${path.basename(imagePath)}`
                })
            );

            await this.processScreenshot(imagePath, metadata, outputDir);
        }

        if (this.missingTiles.length > 0) {
            console.warn('\n以下切片在处理过程中缺失或失败:');
            this.missingTiles.forEach((tile) => {
                console.warn(`- Zoom: ${tile.zoom}, X: ${tile.x !== null ? tile.x : 'N/A'}, Y: ${tile.y !== null ? tile.y : 'N/A'}, 原因: ${tile.reason}`);
                if (tile.error) {
                    console.warn(`  错误信息: ${tile.error}`);
                }
            });
        } else {
            console.log('\n所有切片已成功处理。');
        }

        this.app.server.broadcast(
            JSON.stringify({
                event: 'import_end',
                data: `所有切片已成功处理。`
            })
        );
    }
}

module.exports = MapTileManager;
